/*
    Contact info:
    bhepple@freeshell.org
    http://bhepple.freeshell.org/scrmgr

    Copyright (C) 1991-2008 Bob Hepple

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; see the file COPYING.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.

*/
/* smread.c */
#include <time.h>
#include <setjmp.h>

#define SM_CLASS extern    
#include "smlib.h"
#include "smio.h"

CHAR *smread_rcsid(void) {
    return("$Id: smread.c,v 1.6 2008/02/19 13:15:50 bhepple Exp $");
}

static int	sm_line_num = 0;

/* Base data structures for holding screen, field, skey etc definitions.
   If something is added, then remember to accomodate the change in the 
   reading routines and, separately, in the writing routines, as well as
   in the .dat files themselves.
*/

int  sm_wr_num_file_fields 			= SM_NUM_FILE_FIELDS;
int  sm_rd_num_file_fields			= 0;
int *sm_rd_file_line_order 			= NULL;
int  sm_wr_file_line_order[SM_NUM_FILE_FIELDS] 	= {
    SM_TAG_F,
    SM_SEPARATOR_F
};

int  sm_wr_num_fld_fields 			= SM_NUM_FLD_FIELDS;
int  sm_rd_num_fld_fields			= 0;
int *sm_rd_fld_line_order			= NULL;
int  sm_wr_fld_line_order[SM_NUM_FLD_FIELDS] 	= {
    SM_NAME_F,
    SM_ROW_F,
    SM_COL_F,
    SM_ATTRIBUTE_F,
    SM_HIDDEN_F,
    SM_PROTECT_F,
    SM_FTYPE_F,
    SM_LEN_F,
    SM_MAXLEN_F,
    SM_C_DEFAULT_F,
    SM_KEYSCREEN_F,
    SM_TRIGGER_FN_F,
    SM_NEXT_FIELD_F
    };
    
int  sm_wr_num_bg_fields 			= SM_NUM_BG_FIELDS;
int  sm_rd_num_bg_fields			= 0;
int *sm_rd_bg_line_order			= NULL;
int  sm_wr_bg_line_order[SM_NUM_BG_FIELDS] 	= {
    SM_TEXT_F,
    SM_ROW_F,
#if 0
    SM_COL_F,
    SM_ATTRIBUTE_F
#endif
    };

int  sm_wr_num_scr_fields 			= SM_NUM_SCR_FIELDS;
int  sm_rd_num_scr_fields			= 0;
int *sm_rd_scr_line_order			= NULL;    
int  sm_wr_scr_line_order[SM_NUM_SCR_FIELDS] 	= {
    SM_NAME_F,
    SM_SCROLLING_F,
    SM_CONSTANT_START_F,
    SM_CONSTANT_END_F,
    SM_FILENAME_F,
    SM_TEMP_STORAGE_F,
    SM_FIRST_FIELD_F,
    SM_SCREEN_SOFTKEY_F,
    SM_AUTO_ADJUST_F,
    SM_SORT_FIELD_F,
    SM_SORT_IGNORE_CASE_F,
    SM_SORT_MONTH_F,
    SM_SORT_NUMERIC_F,
    SM_SORT_REVERSE_F,
    SM_SCREEN_FIELDNAMES_F	/* MUST BE LAST FIELD */
    };
    
int  sm_wr_num_menu_fields 			= SM_NUM_MENU_FIELDS;
int  sm_rd_num_menu_fields			= 0;
int *sm_rd_menu_line_order 			= NULL;
int  sm_wr_menu_line_order[SM_NUM_MENU_FIELDS] 	= {
    SM_NAME_F,
    SM_MENU_TYPE_F,
    SM_MENU_TEXT_F,
    SM_MENU_COMMAND_F
    };

int  sm_wr_num_skey_fields 			= SM_NUM_SKEY_FIELDS;
int  sm_rd_num_skey_fields			= 0;
int *sm_rd_skey_line_order 			= NULL;
int  sm_wr_skey_line_order[SM_NUM_SKEY_FIELDS] 	= {
SM_NAME_F,
SM_UPPER_F,SM_LOWER_F,SM_KEY_NAME_F,SM_KEY_HELP_F,SM_CALLBACK_FN_F,SM_TOGGLE_F,
SM_UPPER_F,SM_LOWER_F,SM_KEY_NAME_F,SM_KEY_HELP_F,SM_CALLBACK_FN_F,SM_TOGGLE_F,
SM_UPPER_F,SM_LOWER_F,SM_KEY_NAME_F,SM_KEY_HELP_F,SM_CALLBACK_FN_F,SM_TOGGLE_F,
SM_UPPER_F,SM_LOWER_F,SM_KEY_NAME_F,SM_KEY_HELP_F,SM_CALLBACK_FN_F,SM_TOGGLE_F,
SM_UPPER_F,SM_LOWER_F,SM_KEY_NAME_F,SM_KEY_HELP_F,SM_CALLBACK_FN_F,SM_TOGGLE_F,
SM_UPPER_F,SM_LOWER_F,SM_KEY_NAME_F,SM_KEY_HELP_F,SM_CALLBACK_FN_F,SM_TOGGLE_F,
SM_UPPER_F,SM_LOWER_F,SM_KEY_NAME_F,SM_KEY_HELP_F,SM_CALLBACK_FN_F,SM_TOGGLE_F,
SM_UPPER_F,SM_LOWER_F,SM_KEY_NAME_F,SM_KEY_HELP_F,SM_CALLBACK_FN_F,SM_TOGGLE_F
};

/*
 *
 * File and data block opening - thoughts towards self-describing files
 *
 */

static int atob ARG1(CHAR *, line)
{
    assert(line);

    if ((*line == 0) || (*line == '0') || (toupper(*line) == 'N'))
	return(0);
    else
	return(1);
}

static CHAR *read_field_name ARG2(CHAR *, oldname, CHAR *, newname)
{
    assert(newname);

    if (newname && (*newname == '$'))
	newname++;
    return(sm_assign(oldname, newname, 0));
}

/* Read a field record */
SM_FIELD sm_read_fld ARG3(SM_SCREEN, screen, SM_FIELD, field, CHAR *, line)
{
    int		ordinal = 0;
    CHAR	*sep, *term;
    SM_FIELD	*fieldp;

    assert(line);

    if (screen == NULL) {
	sm_line_error("missing screen definition", line, sm_line_num);
	return(NULL);
    }
    
    term = strchr(line, 0);
    sep = sm_parse(line, 0, sm_default_separator);
    line = sm_trim(line);
    
    /* Skip first field (record type) */
    line = sm_nextfield(sm_default_separator, line, sep, 0);
    sep = sm_parse(line, 0, sm_default_separator);
    
    while ((line < term) && (ordinal < sm_rd_num_fld_fields)) {
	if (field == NULL) {
	    fieldp = sm_create_fld(screen);
	    field = *fieldp;
	}
	
	switch (sm_rd_fld_line_order[ordinal]) {
	case SM_NAME_F:
	    line = sm_trim(line);
	    field->field_name = read_field_name(field->field_name, line);
	    break;
	case SM_ROW_F:
	    field->row = atoi(line);
	    break;
	case SM_COL_F:
	    field->col = atoi(line);
	    break;
	case SM_ATTRIBUTE_F:
	    field->attribute = atoi(line);
	    break;
	case SM_HIDDEN_F:
	    line = sm_trim(line);
	    field->hidden = atob(line);
	    break;
	case SM_PROTECT_F:
	    line = sm_trim(line);
	    field->protect = atob(line);
	    break;
	case SM_FTYPE_F:
	    field->ftype = atoi(line);
	    field->has_popup = 0;
	    field->non_blank = 0;
	    field->disallow_chdir = 0;
	    field->disallow_insert = 0;
	    if (field->ftype >= 400) {
		field->ftype -= 400;
		field->disallow_chdir = 1;
		field->disallow_insert = 1;
	    }
	    if (field->ftype >= 200) {
		field->ftype -= 200;
		field->has_popup = 1;
	    }
	    if (field->ftype >= 100) {
		field->ftype -= 100;
		field->non_blank = 1;
	    }

	    /* File or Directory field types */
	    if ((field->ftype == 13) || (field->ftype == 15))
		field->disallow_insert = 1;

	    if (field->ftype == SM_TIMESTAMP_FT)
		screen->timestamp_field = field;
	    break;
	case SM_LEN_F:
	    field->len = atoi(line);
	    break;
	case SM_MAXLEN_F:
	    field->max_len = atoi(line);
	    break;
	case SM_C_DEFAULT_F: 
	    SM_ASSIGN(field->c_default, line);
	    break;
	case SM_KEYSCREEN_F:
	    line = sm_trim(line);
	    if (*line)
		SM_ASSIGN(field->master_screen_name, line);
	    else {
		if (field->master_screen_name)
		    free(field->master_screen_name);
		field->master_screen_name = NULL;
	    }
	    break;
	case SM_TRIGGER_FN_F:
	    line = sm_trim(line);
	    if (*line)
		SM_ASSIGN(field->trigger_fn, line);
	    else {
		if (field->trigger_fn)
		    free(field->trigger_fn);
		field->trigger_fn = NULL;
	    }
	    break;
	case SM_NEXT_FIELD_F:
	    line = sm_trim(line);
	    if (*line)
		field->next_field = read_field_name(field->next_field, line);
	    else {
		if (field->next_field)
		    free(field->next_field);
		field->next_field = NULL;
	    }
	    break;

	default:
	    break;
	}
	line = sm_nextfield(sm_default_separator, line, sep, 0);
	ordinal++;
	sep = sm_parse(line, 0, sm_default_separator);
    }
    return(field);
}

/* Read a global record - actually the file header */
/* Bit nasty - assumes that sort keys follow the other data and that
   ascending and ignore_case flags follow the sort keys */
void sm_read_gbl ARG2(SM_FILE, screenset, CHAR *, line)
{
    int		ordinal = 0;
    CHAR	*sep, *term;

    assert(screenset && line);

    term = strchr(line, 0);

    /* Skip first field (record type) */
    sep = sm_parse(line, 0, sm_default_separator);
    line = sm_nextfield(sm_default_separator, line, sep, 0);
    
    while ((line < term) && (ordinal < sm_rd_num_file_fields)) {
	
	switch (sm_rd_file_line_order[ordinal]) {
	case SM_TAG_F:
	    sep = sm_parse(line, 0, sm_default_separator);
	    screenset->tagname = read_field_name(screenset->tagname, line);
	    if (screenset->tagname && *(screenset->tagname) == 0) {
		free(screenset->tagname);
		screenset->tagname = NULL;
	    }

	    line = sm_nextfield(sm_default_separator, line, sep, 0);
	    break;
	    
	case SM_SEPARATOR_F:
	    sep = sm_parse(line, 1, sm_default_separator);
	    if (*line == 't')
		screenset->separator = '\t';
	    else if (*line == '0')
		screenset->separator = 0;
	    else
		screenset->separator = *line;
	    line = sm_nextfield(sm_default_separator, line, sep, 1);
	    if ((screenset->separator == ' ') ||
		(screenset->separator == '\t'))
		screenset->untabify = 0;
	    else
		screenset->untabify = 1;
	    break;
	
	default:
	    break;
	}
	ordinal++;
    }
}

/* Read a screen record */
SM_SCREEN sm_read_scr ARG3(SM_FILE, screenset, SM_SCREEN, screen, CHAR *, line)
{
    int		ordinal = 0;
    CHAR	*sep, *term;
    SM_SCREEN	*screenp;
    SM_SORTKEY	thiskey = NULL;

    assert(screenset && line);

    term = strchr(line, 0);
    sep = sm_parse(line, 0, sm_default_separator);
    line = sm_trim(line);
    
    /* Skip first field (record type) */
    line = sm_nextfield(sm_default_separator, line, sep, 0);
    sep = sm_parse(line, 0, sm_default_separator);
    line = sm_trim(line);
    
    while ((line < term) && (ordinal < sm_rd_num_scr_fields)) {
	switch (sm_rd_scr_line_order[ordinal]) {
	case SM_NAME_F:
	    if ((screen == NULL) &&
		((screen = sm_scr_open(screenset, line)) == NULL)) {
		if (screen == NULL) {
		    screenp = sm_create_scr(screenset);
		    screen = *screenp;
		}
		SM_ASSIGN(screen->screen_name, line);
	    } else
		SM_ASSIGN(screen->screen_name, line);
	    break;
	    
	case SM_SCROLLING_F:
	    screen->scrolling = atob(line);
	    break;

	case SM_CONSTANT_START_F:
	    screen->constant_start = read_field_name(screen->constant_start, 
						     line);
	    if (screen->constant_start && (*screen->constant_start == 0)) {
		free(screen->constant_start);
		screen->constant_start = NULL;
	    }
	    break;

	case SM_CONSTANT_END_F:
	    screen->constant_end = read_field_name(screen->constant_end,
						   line);
	    if (screen->constant_end && (*screen->constant_end == 0)) {
		free(screen->constant_end);
		screen->constant_end = NULL;
	    }
	    break;

	case SM_FILENAME_F:
	    if (*line)
		SM_ASSIGN(screen->filename, line);
	    else {
		if (screen->filename)
		    free(screen->filename);
		screen->filename = NULL;
	    }
	    break;

	case SM_TEMP_STORAGE_F:
	    sm_toupper(line);
	    if (*line && (int) strchr("MFD", *line))
		screen->temp_storage = *line;
	    break;

	case SM_FIRST_FIELD_F:
	    if (*line)
		screen->first_field = read_field_name(screen->first_field, 
						      line);
	    else {
		if (screen->first_field)
		    free(screen->first_field);
		screen->first_field = NULL;
	    }
	    break;

	case SM_SCREEN_SOFTKEY_F:
	    if (*line)
		SM_ASSIGN(screen->softkey, line);
	    else {
		if (screen->softkey)
		    free(screen->softkey);
		screen->softkey = NULL;
	    }
	    break;

	case SM_AUTO_ADJUST_F:
	    screen->auto_adjust = atob(line);
	    break;

	case SM_SORT_FIELD_F:
	    if (*line) {
		/* Create a new sortkey */
		thiskey = *sm_create_sortkey(screen);
		thiskey->sortfield_name = 
		    read_field_name(thiskey->sortfield_name, line);
	    } else /* sortkey is blank */
		thiskey = NULL;
	    break;

	case SM_SORT_REVERSE_F:
	    if (thiskey)
		thiskey->reverse = atob(line);
	    break;

	case SM_SORT_IGNORE_CASE_F:
	    if (thiskey)
		thiskey->ignore_case = atob(line);
	    break;

	case SM_SORT_MONTH_F:
	    if (thiskey)
		thiskey->month = atob(line);
	    break;

	case SM_SORT_NUMERIC_F:
	    if (thiskey)
		thiskey->numeric = atob(line);
	    break;

	case SM_SCREEN_FIELDNAMES_F:
	    /* Read in the data descriptions for this screen */
	    while (*line) {
		CHAR	*p = xmalloc((INDEX) (strlen(line) + 1));

		strcpy(p, line);
		screen->data_field = addToArray(screen->data_field, p);
		
		line = sm_nextfield(sm_default_separator, line, sep, 0);
		sep = sm_parse(line, 0, sm_default_separator);
		line = sm_trim(line);
	    }
	    
	    break;
	    
	default:
	    break;
	}
	line = sm_nextfield(sm_default_separator, line, sep, 0);
	ordinal++;
	sep = sm_parse(line, 0, sm_default_separator);
	line = sm_trim(line);
    }

    if (screen->constant_start && screen->constant_end &&
	(strcmp(screen->constant_start, screen->constant_end) == 0)) {
	free(screen->constant_start);
	screen->constant_start = NULL;
	free(screen->constant_end);
	screen->constant_end = NULL;
    }

    return(screen);
}

/* Read a background detail record */
/* Once upon a time, there could be many bg's for each line. Therefore
   we still need to allow for this even though now we only allow one
   per row. */
SM_BACKGROUND sm_read_bg ARG3(SM_SCREEN, screen, SM_BACKGROUND, bg, CHAR *, line) 
{
    int		ordinal = 0, row = 0, col = 0;
    CHAR	*sep, *term;
    STRING	text = NULL;

    if (screen == NULL) {
	sm_line_error("missing screen definition", line, sm_line_num);
	return(NULL);
    }
    
    term = strchr(line, 0);
    sep = sm_parse(line, 0, sm_default_separator);
    line = sm_trim(line);
    
    /* Skip first field (record type) */
    line = sm_nextfield(sm_default_separator, line, sep, 0);
    sep = sm_parse(line, 0, sm_default_separator);
    
    while ((line < term) && (ordinal < sm_rd_num_bg_fields)) {
	
	switch (sm_rd_bg_line_order[ordinal]) {
	case SM_TEXT_F:
	    if (text)
		freeString(text);
	    text = StrToString(NULL, line);
	    break;
	    
	case SM_ROW_F:
	    row = atoi(line);
	    break;
	    
	case SM_COL_F:
#if 0
	    bg->col = atoi(line);
#else /* allow for old .dat files with a 'col' parameter */
	    col = atoi(line);
#endif /* 0 */
	    break;
	    
	case SM_ATTRIBUTE_F:
#if 0
	    bg->attribute = atoi(line);
#endif
	    break;
	    
	default:
	    break;
	}
	line = sm_nextfield(sm_default_separator, line, sep, 0);
	ordinal++;
	sep = sm_parse(line, 0, sm_default_separator);
    }

    if ((bg = sm_pick_bg(screen, row)) == NULL) {
	bg = sm_create_bg(screen);
	bg->row = row;
    }

    if (bg->text == NULL)
	bg->text = newString(0);
    setCharInString(bg->text, col, ' ');
    truncString(bg->text, col);
    StringCat(bg->text, text);
    freeString(text);
    return(bg);
}

/* Read a softkey-set record */
/* This is a bit nasty at present - the routine assumes that there is
   one set-name field followed by 8 upper;lower;key_name;toggle fields -
   and then 8 help strings - it also assumes that the toggle is the 
   last field */

SM_SOFTKEY sm_read_skey ARG3(SM_FILE, screenset,SM_SOFTKEY, key, CHAR *, line) 
{
    int		ordinal = 0, 
    		skey_num = 0,
    		i;
    CHAR	*sep, *term;	
    
    assert(screenset && line);

    term = strchr(line, 0);

    /* Skip first field (record type) */
    sep = sm_parse(line, 0, sm_default_separator);
    line = sm_nextfield(sm_default_separator, line, sep, 0);
    
    if (key == NULL)
	key = sm_create_skey(screenset);
    
    while ((line < term) && (ordinal < sm_rd_num_skey_fields)) {
	
	i = sm_rd_skey_line_order[ordinal];
	
	switch (i) {
	case SM_NAME_F:
	    sep = sm_parse(line, 0, sm_default_separator);
	    line = sm_trim(line);
	    SM_ASSIGN(key->skey_name, line);
	    line = sm_nextfield(sm_default_separator, line, sep, 0);
	    break;
	    
	case SM_LOWER_F:
	    sep = sm_parse(line, 8, sm_default_separator);
	    SM_FASSIGN(key->lower[skey_num], line, 8);
	    line = sm_nextfield(sm_default_separator, line, sep, 8);
	    break;
	    
	case SM_UPPER_F:
	    sep = sm_parse(line, 8, sm_default_separator);
	    SM_FASSIGN(key->upper[skey_num], line, 8);
	    line = sm_nextfield(sm_default_separator, line, sep, 8);
	    break;
	    
	case SM_KEY_NAME_F:
	    sep = sm_parse(line, 0, sm_default_separator);
	    line = sm_trim(line);
	    SM_ASSIGN(key->key_name[skey_num], line);
	    line = sm_nextfield(sm_default_separator, line, sep, 0);
	    break;
	    
	case SM_TOGGLE_F:
	    sep = sm_parse(line, 1, sm_default_separator);
	    if (atob(line))
		key->toggle[skey_num++] = FALSE;
	    else
		key->toggle[skey_num++] = SM_NOT_TOGGLE;

	    line = sm_nextfield(sm_default_separator, line, sep, 1);
	    break;

	case SM_KEY_HELP_F:
	    sep = sm_parse(line, 0, sm_default_separator);
	    line = sm_trim(line);
	    SM_ASSIGN(key->key_help[skey_num], line);
	    line = sm_nextfield(sm_default_separator, line, sep, 0);
	    break;

	case SM_CALLBACK_FN_F:
	    sep = sm_parse(line, 0, sm_default_separator);
	    line = sm_trim(line);
	    SM_ASSIGN(key->key_argument[skey_num], line);
	    line = sm_nextfield(sm_default_separator, line, sep, 0);
	    break;

	default:
	    break;
	}
	ordinal++;
    }
    return(key);
}

SM_MENU sm_read_menu ARG2(SM_FILE, screenset, CHAR *, line) 
{
    int			ordinal = 0, i;
    CHAR		*sep, *term, *text, *command, *p;	
    SM_MENU		menu = NULL;
    SM_MENU_ITEM	item = NULL;

    assert(screenset && line);

    term = strchr(line, 0);

    /* Skip first field (record type) */
    sep = sm_parse(line, 0, sm_default_separator);
    line = sm_nextfield(sm_default_separator, line, sep, 0);
    
    while ((line < term) && (ordinal < sm_rd_num_menu_fields)) {
	
	i = sm_rd_menu_line_order[ordinal];

	switch (i) {
	case SM_NAME_F:
	    sep = sm_parse(line, 0, sm_default_separator);
	    line = sm_trim(line);
	    if ((menu = sm_menu_open(screenset, line)) == NULL) {
		menu = sm_create_menu(screenset);
		SM_ASSIGN(menu->menu_name, line);
	    }
	    line = sm_nextfield(sm_default_separator, line, sep, 0);
	    break;
	    
	case SM_MENU_TYPE_F:
	    sep = sm_parse(line, 0, sm_default_separator);
	    menu->type = atoi(line);
	    line = sm_nextfield(sm_default_separator, line, sep, 0);
	    break;
	    
	case SM_MENU_TEXT_F:
	    sep = sm_parse(line, 0, sm_default_separator);
	    text = xmalloc((INDEX) (strlen(line) + 1));
	    strcpy(text, line);
	    item = (SM_MENU_ITEM) xmalloc(sizeof(struct sm_menu_item));
	    item->text = text;
	    item->start = 0;
	    if ((p = strchr(text, '\\')) == NULL)
		item->key = 0;
	    else
		item->key = SM_META(*++p);

	    menu->items = addToArray(menu->items, (void *) item);
	    line = sm_nextfield(sm_default_separator, line, sep, 0);
	    break;
	    
	case SM_MENU_COMMAND_F:
	    sep = sm_parse(line, 0, sm_default_separator);
	    command = xmalloc((INDEX) (strlen(line) + 1));
	    strcpy(command, line);
	    item->command = command;
	    line = sm_nextfield(sm_default_separator, line, sep, 0);
	    break;
	    
	default:
	    break;
	}
	ordinal++;
    }
    return(menu);
}

/* Read one of the record description records. These define what will
   appear later in the file */
static int sm_rebuild ARG3(int **, table, int *, num_entries, CHAR *, line)
{
    int		i, ordinal = 0;
    CHAR	*sep, *term;

    assert(table && num_entries && line);

    if (*table == NULL) {
	*table = (int *) xmalloc((INDEX) sizeof(int) * *num_entries);
	for (i = 0; i < *num_entries; i++)
	    (*table)[i] = 0;
    }

    term = strchr(line, 0);
    sep = sm_parse(line, 0, sm_default_separator);
    line = sm_trim(line);
    while (line < term) {
	sm_toupper(line); /* supposed to be a keyword */
	i = sm_lookup_keyword(sm_keyword, line);
	if (i < 0)
	    sm_line_error("This version does not understand: ", line, sm_line_num);
	else {
	    /* Correct size of array ... */
	    *table = (int *) xrealloc((CHAR *)(*table),
				      (INDEX) (sizeof(int) * (ordinal + 1)));
	    (*table)[ordinal++] = i;
	}
	line = sm_nextfield(sm_default_separator, line, sep, 0);
	sep = sm_parse(line, 0, sm_default_separator);
	line = sm_trim(line);
    }

    *num_entries = ordinal;
    return(0);
}

static int sm_parse_line ARG3(SM_FILE, 		screenset,
			      CHAR *, 		line, 
			      SM_SCREEN *, 	screenp)
{
    CHAR	*sep;
    int		record_type;

    assert(screenset && line);

    /* remove leading whitespace */
    while (line && isspace(*line))
	line++;
    
    /* ignore commments */
    if (!*line || *line == '#')
	return(0);
    
    /* get the first keyword */
    sep = sm_parse(line, 0, sm_default_separator);
    line = sm_trim(line);
    
    sm_toupper(line);
    
    record_type = sm_lookup_keyword(sm_keyword, line);
    *sep = sm_default_separator;

    switch (record_type) {
    case SM_FILE_TABLE_F:
	return(sm_rebuild(&sm_rd_file_line_order,
			  &sm_rd_num_file_fields,
			  sep + 1));

    case SM_SCR_TABLE_F:
	return(sm_rebuild(&sm_rd_scr_line_order,
			  &sm_rd_num_scr_fields,
			  sep + 1));
	
    case SM_FLD_TABLE_F:
	return(sm_rebuild(&sm_rd_fld_line_order,  
			  &sm_rd_num_fld_fields,
			  sep + 1));
	
    case SM_BG_TABLE_F:
	return(sm_rebuild(&sm_rd_bg_line_order,	 
			  &sm_rd_num_bg_fields,
			  sep + 1));

    case SM_SKEY_TABLE_F:
	return(sm_rebuild(&sm_rd_skey_line_order,  
			  &sm_rd_num_skey_fields,
			  sep + 1));

    case SM_MENU_TABLE_F:
	return(sm_rebuild(&sm_rd_menu_line_order,  
			  &sm_rd_num_menu_fields,
			  sep + 1));

    case SM_FILE_F:
	sm_read_gbl(screenset, line);
	return(0);

    case SM_FIELD_F:
	if (*screenp == NULL) {
	    sm_line_error("missing screen definition", line, sm_line_num);
	    return(SM_ERROR);
	}
	if ((sm_read_fld(*screenp, NULL, line)) == NULL)
	    return(SM_ERROR);
	else
	    return(0);
	
    case SM_BG_F:
	if (*screenp == NULL) {
	    sm_line_error("missing screen definition", line, sm_line_num);
	    return(SM_ERROR);
	}
	if ((sm_read_bg(*screenp, NULL, line)) == NULL)
	    return(SM_ERROR);
	else
	    return(0);
	
    case SM_SCREEN_F:
	if ((*screenp = sm_read_scr(screenset, NULL, line)) == NULL)
	    return(SM_ERROR);
	else
	    return(0);
	
    case SM_KEY_F:
	if ((sm_read_skey(screenset, NULL, line)) == NULL)
	    return(SM_ERROR);
	else
	    return(0);
	
    case SM_MENU_F:
	if ((sm_read_menu(screenset, line)) == NULL)
	    return(SM_ERROR);
	else
	    return(0);
	
    default:
	sm_line_error("bad line", line, sm_line_num);
	return(SM_ERROR);
    }
}

static int *copy_table ARG3(int *, old_table, int *, table, int, size)
{
    int		*retval;
    int		i;

    if (old_table)
	free(old_table);

    retval = (int *) xmalloc((INDEX) sizeof(int) * size);
    for (i = 0; i < size; i++)
	retval[i] = table[i];
    return(retval);
}

static void copy_tables()
{
    /* Use the write-record definitions as defaults */
    sm_rd_num_fld_fields	= sm_wr_num_fld_fields;
    sm_rd_num_bg_fields		= sm_wr_num_bg_fields;
    sm_rd_num_scr_fields	= sm_wr_num_scr_fields;
    sm_rd_num_skey_fields	= sm_wr_num_skey_fields;
    sm_rd_num_menu_fields	= sm_wr_num_menu_fields;
    sm_rd_num_file_fields	= sm_wr_num_file_fields;

    sm_rd_file_line_order	= copy_table(sm_rd_file_line_order,
					     sm_wr_file_line_order,
					     sm_wr_num_file_fields);
    sm_rd_fld_line_order	= copy_table(sm_rd_fld_line_order,
					     sm_wr_fld_line_order,
					     sm_wr_num_fld_fields);
    sm_rd_bg_line_order		= copy_table(sm_rd_bg_line_order,
					     sm_wr_bg_line_order,
					     sm_wr_num_bg_fields);
    sm_rd_scr_line_order	= copy_table(sm_rd_scr_line_order,
					     sm_wr_scr_line_order,
					     sm_wr_num_scr_fields);
    sm_rd_menu_line_order	= copy_table(sm_rd_menu_line_order,
					     sm_wr_menu_line_order,
					     sm_wr_num_menu_fields);
    sm_rd_skey_line_order	= copy_table(sm_rd_skey_line_order,
					     sm_wr_skey_line_order,
					     sm_wr_num_skey_fields);
}

/* If there is a data_field array, then check that all the fields
   have an entry and vice versa ... */
int sm_check_data_field ARG1(SM_SCREEN, screen)
{
    SM_FIELD	*fp;
    CHAR	**p;
    INDEX	i = 0;
    int		retval = 0;
    
    assert(screen);

    if (screen->data_field == NULL)
	return(0);

    for (fp = sm_first_fld(screen);
	 fp && *fp;
	 fp = sm_next_fld(fp)) {
	
	for (i = 0, p = (CHAR **) firstArrayItemp(screen->data_field); 
	     p && *p; 
	     p = (CHAR **) nextArrayItemp(screen->data_field, (void **) p), i++)
	    if (strcmp(*p, (*fp)->field_name) == 0)
		break;

	if (!p || !*p) {
	    CHAR *newEntry = xmalloc((INDEX) (strlen((*fp)->field_name) + 1));

	    strcpy(newEntry, (*fp)->field_name);
	    screen->data_field = addToArray(screen->data_field, newEntry);
	    retval = 1;
	}
    }
	
    /* Now make sure that all the items in the data_field array
       correspond to a field */
    for (p = (CHAR **) firstArrayItemp(screen->data_field); 
	 p && *p;) {
	if (sm_fld_open(screen, *p) == NULL) {
	    free(*p);
	    p = (CHAR **) removeFromArrayp(screen->data_field, (void **) p);
	    retval = 1;
	} else
	    p = (CHAR **) nextArrayItemp(screen->data_field, (void **) p);
    }
    return(retval);
}

/* purely for qsort in sm_order_scr ... */
static int _sm_fieldsort ARG2(const void *, field1, const void *, field2)
{
    assert(field1 && field2);

    if ((*(SM_FIELD *) field1)->row > (*(SM_FIELD *) field2)->row)
	return(1);    
    if ((*(SM_FIELD *) field1)->row < (*(SM_FIELD *) field2)->row)
	return(-1);
    if ((*(SM_FIELD *) field1)->col > (*(SM_FIELD *) field2)->col)
	return(1);
    if ((*(SM_FIELD *) field1)->col < (*(SM_FIELD *) field2)->col)
	return(-1);
    return(0);
}

/* purely for qsort in sm_order_scr ... */
static int _sm_bgsort ARG2(const void *, bg1, const void *, bg2)
{
    assert(bg1 && bg2);

    if ((*(SM_BACKGROUND *)bg1)->row > (*(SM_BACKGROUND *)bg2)->row)
	return(1);
    if ((*(SM_BACKGROUND *)bg1)->row < (*(SM_BACKGROUND *)bg2)->row)
	return(-1);
    return(0);
}

/* Sort BG and fields into row then column order */
static void sm_order_scr ARG1(SM_SCREEN, screen)
{
    int			num;

    assert(screen);

    /* Order backgrounds */
    num = arrayLength(screen->background);
    qsort((CHAR *) sm_first_bg(screen), num, sizeof(SM_BACKGROUND *), _sm_bgsort);
    
    /* Order fields */
    num = arrayLength(screen->field);
    qsort((CHAR *) sm_first_fld(screen), num, sizeof(SM_FIELD *), _sm_fieldsort);
}

/* Find the last field row for scrolling screens */
void sm_calc_scrolling_line ARG1(SM_SCREEN, screen)
{
    SM_FIELD	*fieldp;
    int		last_row;
    
    assert(screen);

    if (screen->scrolling) {
	last_row = 0;
	for (fieldp = sm_first_fld(screen); 
	     fieldp && *fieldp; 
	     fieldp = sm_next_fld(fieldp))
	    if ((*fieldp)->row > last_row)
		last_row = (*fieldp)->row;
	screen->scrolling = last_row + 1;
    }
}

/* In case the user prefers numeric tags and constant_ values ... */
static CHAR *convert_num_to_fieldname ARG3(SM_SCREEN, 	screen, 
					   CHAR *, 	buf,
					   int,		offset)
{
    INDEX	fieldno, num;
    SM_FIELD	*fieldp, field;
    CHAR	*p;

    assert(screen);

    if (buf && (*buf >= '0') && (*buf <= '9')) {
	
	num = atoi(buf) + offset;
	free(buf);
	buf = NULL;
	if (num > 0) {
	    fieldp = sm_first_fld(screen);
	    if (fieldp)
		field = *fieldp;
	    else
		field = NULL;
	    for (fieldno = 1; field; fieldno++) {
		if (screen->data_field && 
		    (p = arrayItem(screen->data_field, fieldno - 1))) {
		    field = sm_fld_open(screen, p);
		} else {
		    /* No screen data descriptions - use fields sequentially */
		    field = *fieldp;
		    fieldp = sm_next_fld(fieldp);
		}
		if (field && (fieldno == num)) {
		    SM_ASSIGN(buf, field->field_name);
		    break;
		}
	    }
	}
    }
    return(buf);
}

int sm_check_screens ARG1(SM_FILE, screenset)
{
    SM_SCREEN	*screenp;
    int		retval = 0;

    assert(screenset);

    /* Accomodate numbers instead of field_name in tagname */
    if (screenset->tagname && sm_first_scr(screenset))
	screenset->tagname = 
	    convert_num_to_fieldname(*sm_first_scr(screenset), 
				     screenset->tagname,
				     0);

    for (screenp = sm_first_scr(screenset); 
	 screenp && *screenp; 
	 screenp = sm_next_scr(screenp)) {

	if ((*screenp)->constant_start)
	    (*screenp)->constant_start = 
		convert_num_to_fieldname(*screenp,
					 (*screenp)->constant_start,
					 1);

	if ((*screenp)->constant_end)
	    (*screenp)->constant_end = 
		convert_num_to_fieldname(*screenp,
					 (*screenp)->constant_end,
					 1);

	sm_calc_scrolling_line(*screenp);

	if (sm_check_data_field(*screenp))
	    retval = SM_ERROR;

	sm_order_scr(*screenp);
    }
    return(retval);
}

int sm_fopen ARG2(SM_FILE, screenset, CHAR *, file_name)
{
    FILE	*f;
    CHAR	*pos, *end, *buf, *backslash;
    SM_SCREEN	screen = NULL;

    assert(screenset && file_name);

    buf = alloca(SM_MAXREC);
    	
    if ((f = fopen(file_name, "r")) == NULL) {
	sm_file_error("Can\'t read file: ", file_name, errno);
	return(SM_ERROR);
    }

    if (sm_rd_file_line_order == NULL)
	copy_tables();

    pos = buf;
    while (fgets(pos, SM_MAXREC - (pos - buf), f) != NULL) {
	sm_line_num++;
	
	/* Bark at long lines - but try and continue */
	if (strlen(pos) >= SM_MAXREC - (pos - buf) - 1)
	    sm_line_error("line too long", buf, sm_line_num);
	
	if ((end = strchr(pos, '\n')) != NULL)
	    *end = 0;
	if ((end = strchr(pos, '\r')) != NULL)
	    *end = 0;

	end = strchr(pos, 0);
	*(end + 1) = 0; /* extra 0 */

	if (*--end == '\\') {
	    *end = 0;
	    pos = end;
	} else {
	    /* Allow " at start and ", at end of line so that .dat file can
	       be #included */
	    if (*buf == '\"') {
		for (pos = buf; *pos; pos++)
		    *pos = *(pos + 1);
		end = strchr(buf, 0);
		if (end > buf)
		    end--;
		while ((end > buf) && isspace(*end))
		    end--;
		if ((*end == ',') && (*--end == '\"'))
		    *end = 0;
	    }

	    /* Contract \\ to \ */
	    pos = buf;
	    while (*pos &&
		   ((backslash = strchr(pos, '\\')) != NULL) &&
		   (*++backslash == '\\')) {
		for (pos = backslash; *pos; pos++)
		    *pos = *(pos + 1);
		pos = backslash + 1;
	    }

	    if (sm_parse_line(screenset, buf, &screen)) {
		fclose(f);
		return(SM_ERROR);
	    }
	    pos = buf;
	}
    }
    fclose(f);

    /* Use write-tables in future */
    copy_tables();
    return(sm_check_screens(screenset));
}

int sm_dbopen ARG2(SM_FILE,	screenset,
		   CHAR **, 	data_block) 
{
    CHAR	*buf, **line;
    SM_SCREEN	screen = NULL;

    assert(screenset);

    if (data_block) {

	buf = alloca(SM_MAXREC);
	
	if (sm_rd_file_line_order == NULL)
	    copy_tables();
    	
	line = data_block;
	while (line && *line) {
	    strcpy(buf, *line);
	    if (sm_parse_line(screenset, buf, &screen))
		return(SM_ERROR);
	    line++;
	}
	
	/* Use write-tables in future */
	copy_tables();
	return(sm_check_screens(screenset));
    } else
	return(0);
}


/* For emacs:
 * Local Variables:
 * tab-width: 8
 * End:
 */
